package
{
	import flash.display.*;
	import flash.events.*;
	import flash.geom.Point;

	public class AngleTool extends CursorTool
	{
		// constants
		public const MAGNET_WIDTH:int = 6;
		public const MAGNET_HEIGHT:int = 50;
		public const JOINT_DIAMETER:int = 8;
		public const MAX_WIGGLE_PROP:Number = .05; // the percent of total wiggle that may be conducted in a single frame
		// public vars
		public var marginOfError_snap:Number, marginOfError_degrees:Number; // Number of pixels to snap to angle, Number of degrees in which parallel lines will snap
		private var _state:String;
		private var _outOfBounds:Boolean = false;
		private var magnet1Point:Point;  // the point and rotation, below, of segment1 once it is set, to adjust relative to current position
		private var magnet1Rotation:Number;
		
		// private objects
		private var drawAngles:Array, oobshapes:Array;
		private var overAngle:DrawAngle, selectedAngle:DrawAngle;
		private var magnet1:Shape,magnet2:Shape;
		private var _angle1:DrawAngle = null;
		private var _angle2:DrawAngle = null;
		private var magnet1good:Boolean = false; // flag for drawing a (blue?) outline around an unset magnet2 indicating that it is ready to be set
		private var magnet2good:Boolean = false; // flag for drawing a (blue?) outline around an unset magnet2 indicating that it is ready to be set
		private var magnet2bad:Boolean = false; // flag for drawing a (red?) outline around an unset magnet2 indicating that it cannot match the given angle
		private var marginOfErrorMessageGiven:Boolean=false;
		private var endpointsPinnedMessageGiven:Boolean=false;
		// for the "wiggle"
		private var initialError:Number; // the error, in degrees, between two "parallel" sides, before wiggle
		private var targetWiggle:Number; // how far the user needs to wiggle, by default min of two angles
		private var currentWiggle:Number; // how far the user has currently wiggled, in eighther direction
		private var previousWigglePoint:Point; // the previous point of this tool so we know how far we travelled in current wiggle
		
		override public function get outOfBounds():Boolean{return _outOfBounds;}
		public function get component1():DrawAngle{return this._angle1;}
		public function set component1(s:DrawAngle):void{this._angle1 = s;}
		public function get component2():DrawAngle{return this._angle2;}
		public function set component2(s:DrawAngle):void{this._angle2 = s;}
		override public function get components():Array
		{
			var arr:Array = new Array();
			if (_angle1 != null) arr.push(_angle1);
			if (_angle2 != null) arr.push(_angle2);
			return arr;
		}
		
		public function AngleTool(drawAngles:Array, oobshapes:Array,marginOfError_degrees:Number,  marginOfError_snap:Number=10)
		{
			super(this);
			this.drawAngles = drawAngles;
			this.oobshapes = oobshapes;
			this.mouseEnabled = false;
			this.mouseChildren = false;
			this.marginOfError_snap = marginOfError_snap;
			this.marginOfError_degrees = marginOfError_degrees;
			this.addEventListener(Event.ADDED, handleAdded);
		}
		private function handleAdded(event:Event):void
		{
			this.removeEventListener(Event.ADDED, handleAdded);
			_state = "zero";
			drawCursor();
			// go through each point and add event listener
			for each (var a:DrawAngle in drawAngles)
			{
				a.mouseEnabled = true;
				if(!a.hasEventListener(MouseEvent.MOUSE_OVER)) a.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverAngle);
			}
			for each (var oobshape:OobShape in oobshapes)
			{
				oobshape.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverOobShape);				
			}
			this.addEventListener(Event.REMOVED, handleRemoved);
		}
		////////////////////////////////// INTERACTIVITY //////////////////////////////////////////
		/** Mouse over should work for all segments to highlight except currently selected, unless allow disconnected */
		private function handleMouseOverAngle(event:MouseEvent):void
		{
			overAngle = DrawAngle(event.currentTarget);
			overAngle.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverAngle);
			overAngle.addEventListener(MouseEvent.MOUSE_OUT, handleMouseOutAngle);
			overAngle.addEventListener(MouseEvent.CLICK, handleClickAngle);
		}
		private function handleMouseOutAngle(event:MouseEvent):void
		{
			overAngle.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutAngle);
			overAngle.removeEventListener(MouseEvent.CLICK, handleClickAngle);
			overAngle.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverAngle);
			overAngle = null;
		}
		private function handleClickAngle (event:MouseEvent):void
		{
			if (selectedAngle != overAngle)
			{
				selectedAngle = overAngle;
				selectComponent(selectedAngle);
			} else
			{
				selectedAngle = null;
				clearComponent(selectedAngle);
			}			
		}
		// out of bounds objects
		private function handleMouseOverOobShape(event:MouseEvent):void
		{
			event.currentTarget.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverOobShape);
			_outOfBounds = true;
			event.currentTarget.addEventListener(MouseEvent.MOUSE_OUT, handleMouseOutOobShape);
		}
		private function handleMouseOutOobShape(event:MouseEvent):void
		{
			event.currentTarget.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutOobShape);
			_outOfBounds = false;
			event.currentTarget.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverOobShape);
		}
		
		////////////// overrides
		
		override public function selectComponent(c:MovieClip):void
		{			
			var a:DrawAngle = DrawAngle(c);
			// select the first angle
			if (_angle1 == null)
			{
				_angle1 = a;
				if (a.hasEventListener(MouseEvent.MOUSE_OUT)) a.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutAngle);
				if (a.hasEventListener(MouseEvent.CLICK)) a.removeEventListener(MouseEvent.CLICK, handleClickAngle);
				if (a.hasEventListener(MouseEvent.MOUSE_OVER)) a.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverAngle);
				_state = "one";
				drawCursor();
			}  else if (_angle2 == null)
			{
				//within margin of error?
				if (magnet2good)
				{
					_angle2 = a;
					if (a.hasEventListener(MouseEvent.MOUSE_OUT)) a.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutAngle);
					if (a.hasEventListener(MouseEvent.CLICK)) a.removeEventListener(MouseEvent.CLICK, handleClickAngle);
					if (a.hasEventListener(MouseEvent.MOUSE_OVER)) a.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverAngle);
					this.x = _angle2.x;
					this.y = _angle2.y;
					this.rotation = _angle2.bisectorRotation;
					// setup wiggle
					initialError = Math.abs(_angle2.degrees - _angle1.degrees);
					targetWiggle = 4*Math.min(_angle2.pointC.drawSegments[0].length, _angle2.pointC.drawSegments[1].length);
					currentWiggle = 0;
					previousWigglePoint = new Point(this.x, this.y);
				}
			} else
			{
				trace("only two may be selected");
			}
			
		}
		override public function clearComponent(c:MovieClip):void
		{
			var a:DrawAngle = DrawAngle(c);
			if (_angle2 != null && _angle2 == a)
			{
				_angle2 = null;
			} else if (_angle2 == null && _angle1 != null && _angle1 == a)
			{
				_angle1 = null;
			}
		}
		
		override public function process(point:Point):void
		{
			var angle:DrawAngle = overAngle;
			
			// First angle not set
			if (_angle1 == null )
			{
				// do we have candidate angle?
				if (angle != null)
				{
					// have we updated the color of the magnet?
					if (!magnet1good)
					{
						magnet1 = drawMagnet(angle.degrees, 1, magnet1);
						magnet1good = true; // so we don't change the graphics every frame
					}
					
					this.x = point.x;
					this.y = point.y;
					this.rotation = angle.bisectorRotation;
				}// no candidate
				else
				{
					if (magnet1good)
					{ // revert magnet to neutral
						magnet1 = drawMagnet(180, 0, magnet1);
						magnet1good = false; // so we don't change the graphics every frame
					}
					this.x = point.x;
					this.y = point.y;
					this.rotation = 0;
				}					
			}// 1) Magnet1 set, looking for a second angle
			else if (_angle1 != null && _angle2 == null)
			{
				// do we have a candidate angle
				if (angle != null && angle != _angle1)
				{
					// have we set either the magnet2good or magnet2bad flags (and therefore do not need further processing?)
					if (!magnet2good && !magnet2bad)
					{
						// good or bad
						//trace("lengths", angle.length, angle1.length, Math.abs(angle.length - angle1.length)); 
						if ( Math.abs(angle.degrees - _angle1.degrees) <= marginOfError_degrees && !(angle.point1.pinned && angle.point2.pinned))
						{ // good
							magnet2 = drawMagnet(_angle1.degrees, 1, magnet2);
							magnet2good = true; // so we don't change the graphics every frame
						} else
						{ // bad
							magnet2 = drawMagnet(_angle1.degrees, -1, magnet2);
							if (!endpointsPinnedMessageGiven && (angle.point1.pinned && angle.point2.pinned))
							{
								GeometryGameItem(parent.parent).newMessage(this, "This angle can't be changed.", magnet2.x, magnet2.y, MessageBox.EPHEMERAL_TYPE);
								endpointsPinnedMessageGiven = true;
							} else if (!marginOfErrorMessageGiven &&  Math.abs(angle.degrees - _angle1.degrees) > marginOfError_degrees)
							{	
								GeometryGameItem(parent.parent).newMessage(this, "These two angle aren't close enough to congruent yet, try dragging first.", magnet2.x, magnet2.y, MessageBox.EPHEMERAL_TYPE);
								marginOfErrorMessageGiven = true;
							}
							magnet2bad = true; // so we don't change the graphics every frame
						}
					}
				} else 
				{ /// No angle here
					if (magnet2good || magnet2bad)
					{						
						magnet2 = drawMagnet(_angle1.degrees, 0, magnet2);
						magnet2good = false; // so we don't change the graphics every frame
						magnet2bad = false; // so we don't change the graphics every frame
					}
				}			
				this.x = point.x;
				this.y = point.y;
				if (magnet2good || magnet2bad){ this.rotation = angle.bisectorRotation;}
				else {this.rotation = _angle1.bisectorRotation;}
				// adjust magnet1
				magnet1.x = this.globalToLocal(magnet1Point).x;
				magnet1.y = this.globalToLocal(magnet1Point).y;
				magnet1.rotation = magnet1Rotation - rotation;
				
			} // 3) angle1 and angle2 are set
			else if (_angle1 != null && _angle2 != null)
			{
				//this.x = angle2.midpoint.x;
				//this.y = point.y;
				
				// amount of wiggle from previous frame
				var thisWiggle:Number = MathUtilities.distance(this.x, this.y, previousWigglePoint.x, previousWigglePoint.y);
				// Should not exceed maxWiggleProp
				var wiggleProp:Number = thisWiggle/targetWiggle;
				if (wiggleProp < MAX_WIGGLE_PROP)
				{
					currentWiggle += thisWiggle;
				} else // in this case use .1 as maxWiggleProp and work backwards to get currentWiggle	
				{
					wiggleProp = MAX_WIGGLE_PROP;
					currentWiggle += wiggleProp * targetWiggle;
				}
								
				// have we reached target
				if (currentWiggle < targetWiggle)
				{
					// adjust angle
					//trace("dilate stuff", _angle2.length, "to", _angle1.length, wiggleProp);
									
					if(_angle2.adjustToRadians(_angle1.radians, currentWiggle/targetWiggle, _angle1))
					{
						previousWigglePoint.x = this.x;
						previousWigglePoint.y = this.y;			
					} else
					{
						//this.dispatchEvent(new Event(Event.COMPLETE));
						GeometryGameItem(parent.parent).processCommand("angle", "in_progress", this, components);
					}										
				} else
				{
					_angle2.adjustToRadians(_angle1.radians, 1, _angle1);
					//this.dispatchEvent(new Event(Event.COMPLETE));
					GeometryGameItem(parent.parent).processCommand("angle", "in_progress", this, components);
				}
				
				this.rotation = _angle2.bisectorRotation;
				this.y = point.y;
				this.x = point.x;
				// adjust magnet1
				magnet1.x = this.globalToLocal(magnet1Point).x;
				magnet1.y = this.globalToLocal(magnet1Point).y;
				magnet1.rotation = magnet1Rotation - rotation;
			}
		}
		
		private function drawCursor():void
		{
			switch (_state)
			{
				case "zero":
					magnet1 = drawMagnet(180); // draw a new magnet
					break;
				case "one":
					//this.removeChild(magnet1);
					magnet1Point = parent.localToGlobal(new Point(_angle1.x, _angle1.y));
					magnet1Rotation = _angle1.bisectorRotation;
					// redraw magnet1 without blue outline
					magnet2 = drawMagnet(_angle1.degrees);
					this.addChild(magnet2);	
					break;
			}
		}
		
		/** Draws a half of the angle tool (two magnets connected)  
		 * State can be in three forms 1-good, 0-default, -1-bad */
		private function drawMagnet(degrees:Number, state:int=0, magnet:Shape=null):Shape
		{
			var hdegrees:Number = Math.abs(degrees)/2;
			if (magnet == null)
			{
				magnet = new Shape();
				this.addChild(magnet);
			} else
			{
				magnet.graphics.clear();
			}
			// draw magnet at one end
			if (state == 1)
			{
				magnet.graphics.lineStyle(2, 0xAAAAFF);
			} else if (state == -1)
			{
				magnet.graphics.lineStyle(2, 0xFFAAAA);
			} else
			{
				magnet.graphics.lineStyle(2, 0xAAAAAA);
			}
			// "hand draw" a rectangle to utilize trig
			var rad:Number = MathUtilities.degToRad(hdegrees);
			var crad:Number = Math.PI/2 - rad;
			//magnet.graphics.drawRect(-MAGNET_WIDTH/2,-MAGNET_HEIGHT/2+armLength/2,MAGNET_WIDTH,MAGNET_HEIGHT);
			magnet.graphics.beginFill(0xCCCCCC);
			magnet.graphics.moveTo(-MAGNET_WIDTH/2*Math.sin(crad), MAGNET_WIDTH/2*Math.cos(crad));
			magnet.graphics.lineTo(-MAGNET_WIDTH/2*Math.sin(crad)-MAGNET_HEIGHT*Math.sin(rad), MAGNET_WIDTH/2*Math.cos(crad)-MAGNET_HEIGHT*Math.cos(rad));
			magnet.graphics.lineTo(MAGNET_WIDTH/2*Math.sin(crad)-MAGNET_HEIGHT*Math.sin(rad), -MAGNET_WIDTH/2*Math.cos(crad)-MAGNET_HEIGHT*Math.cos(rad));
			magnet.graphics.lineTo(MAGNET_WIDTH/2*Math.sin(crad), -MAGNET_WIDTH/2*Math.cos(crad));
			magnet.graphics.lineTo(-MAGNET_WIDTH/2*Math.sin(crad), MAGNET_WIDTH/2*Math.cos(crad));
			magnet.graphics.endFill();
			// other side
			rad = -rad;
			crad = -crad;
			magnet.graphics.beginFill(0xCCCCCC);
			magnet.graphics.moveTo(-MAGNET_WIDTH/2*Math.sin(crad), MAGNET_WIDTH/2*Math.cos(crad));
			magnet.graphics.lineTo(-MAGNET_WIDTH/2*Math.sin(crad)-MAGNET_HEIGHT*Math.sin(rad), MAGNET_WIDTH/2*Math.cos(crad)-MAGNET_HEIGHT*Math.cos(rad));
			magnet.graphics.lineTo(MAGNET_WIDTH/2*Math.sin(crad)-MAGNET_HEIGHT*Math.sin(rad), -MAGNET_WIDTH/2*Math.cos(crad)-MAGNET_HEIGHT*Math.cos(rad));
			magnet.graphics.lineTo(MAGNET_WIDTH/2*Math.sin(crad), -MAGNET_WIDTH/2*Math.cos(crad));
			magnet.graphics.lineTo(-MAGNET_WIDTH/2*Math.sin(crad), MAGNET_WIDTH/2*Math.cos(crad));
			magnet.graphics.endFill();
			
			// draw magnet on the other end
			// Draw rotation joint
			magnet.graphics.lineStyle(1,0xAAAAAA);
			magnet.graphics.beginFill(0xDDDDDD);
			magnet.graphics.drawEllipse(-JOINT_DIAMETER/2, -JOINT_DIAMETER/2, JOINT_DIAMETER, JOINT_DIAMETER);
			magnet.graphics.endFill();
			return magnet;
		}
		
		/** When this is removed we need to remove stuff we placed on the parent object */
		private function handleRemoved(event:Event):void
		{
			//if (magnet1 != null)	
			//{
			//	if (magnet1.parent == this.parent) this.parent.removeChild(magnet1);
			//}
			this.removeEventListener(Event.REMOVED, handleRemoved);
			// go through each point and add event listener
			for each (var a:DrawAngle in drawAngles)
			{
				if(a.hasEventListener(MouseEvent.MOUSE_OVER)) a.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverAngle);
				if(a.hasEventListener(MouseEvent.MOUSE_OUT)) a.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutAngle);
				if(a.hasEventListener(MouseEvent.CLICK)) a.removeEventListener(MouseEvent.CLICK, handleClickAngle);
				a.mouseEnabled = false;
			}
			for each (var oobshape:OobShape in oobshapes)
			{
				if(oobshape.hasEventListener(MouseEvent.MOUSE_OVER)) oobshape.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverOobShape);				
				if(oobshape.hasEventListener(MouseEvent.MOUSE_OUT)) oobshape.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutOobShape);				
			}
		}
	}
}